﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.IO;
using Xww.LCV.Model;
using Xww.IO.Serialize;
using Xww.IO;
using System.Runtime.Serialization;
using Xww.Task;
using System.Text.RegularExpressions;

namespace Xww.LCV {
    [Serializable]
    public class LCSCollection : List<LCS>, ISerializable {
        public LCSCollection(IEnumerable<LCS> lcsArray, string generateInfo) {
            this.AddRange(lcsArray);
            GenerateInfo = generateInfo;
        }
        public LCSCollection() {
            GenerateInfo = "New LCV collection created on : " + DateTime.Now.ToString();
        }

        public string GenerateInfo { get; set; }
        public void AddGenerateInfo(string info) {
            GenerateInfo = string.Concat(GenerateInfo,Environment.NewLine, info);
        }
        public EventHandler<Xww.Task.ProgressStatus> OnStepChange { get; set; }

        public void StepChange(Xww.Task.ProgressStatus e) {
            if (OnStepChange != null)
                OnStepChange(this, e);
        }


        #region File save and read

        ///<summary>将对象输出到文本文件</summary>
        public void SaveToTxt(string fileName) {
            using (StreamWriter writer = new StreamWriter(fileName)) {
                WriteToTextWriter(writer);
            }
        }
    ///<summary>将所有变量输出到标准文本输出流</summary>
        public void WriteToTextWriter(TextWriter writer) {
            writer.WriteLine("# {0}", GenerateInfo.Replace(Environment.NewLine, string.Concat(Environment.NewLine,"#")));
            foreach (LCS item in this) {
                writer.WriteLine("{0} {1}",item, item.Support);
            }
        }
        ///<summary>从文本文件读入对象, 遇到异常则停止所有转换</summary>
        public static LCSCollection LoadFromTxt(string fileName) {
            LCSCollection rst = new LCSCollection();
            StringBuilder sb = new StringBuilder();
            foreach (string line in fileName.ReadAllLines()) {
                if (line.StartsWith("#")) {
                    sb.Append(string.Concat(line, Environment.NewLine));
                    continue;
                }
                rst.Add((LCS)line);
            }
            rst.GenerateInfo = sb.ToString();
            return rst;
        }
        static Xww.IO.Serialize.BinarySerializer<LCSCollection> SERIALIZER = new BinarySerializer<LCSCollection>();
        public static LCSCollection LoadFromLcv(string fileName) {
            return SERIALIZER.DeserializeFromFileStream(fileName);
        }
        public void SaveToBinLcv(string fileName) {
            try {
                SERIALIZER.SerializeToFileStream(this, fileName);
            }
            catch {
                this.SaveToTxt(string.Concat(fileName, ".txt"));
            }
        }
        #endregion


        ///<summary>将 LCS 集合进行精化（去除相对位置关系相同的）</summary>
        public LCSCollection Refine(int threshold) {
            //Refine();
            List<LCS> list = new List<LCS>();
            var groups = this.GroupBy(o => o.Length).OrderBy(o => o.Key);
            for (int i = 0; i < groups.Count() - 1; i++) {
                var item = groups.ElementAt(i);
                var parents = groups.ElementAt(i + 1);
                list.AddRange( item.Where( sub=> {
                    LCS parent = parents.FirstOrDefault(p => p.IsParentOf(sub));
                    return parent == null || sub.Support - parent.Support > threshold;
                }));
            }
            list.AddRange(groups.Last());
            string info = string.Format("After refinement there are {0} LCSs left. [{1}]", list.Count, DateTime.Now.ToString());
            LCSCollection rst = new LCSCollection(list, this.GenerateInfo);
            rst.AddGenerateInfo(info);
            return rst;
        }

        public void Encode(string sourceFile, string outputFile, string lable) {
            Encode(sourceFile, outputFile, lable, false);
        }

        public void Encode(string sourceFile, string outputFile, string lable, bool positionRelated) {
            StreamReader reader;
            StreamWriter writer;
            using(reader = new StreamReader(sourceFile))
            using (writer = new StreamWriter(outputFile)) {
                string line = null;
                while ( (line = reader.ReadLine() )!= null ) {
                    line = line.Trim();
                    if (line == "") { continue; }
                    writer.Write("{0} ",lable);
                    for (int i = 0; i < Count-1; i++) {
                        //writer.Write("{0}:{1} ", i + 1, this[i].CountInSequnece(line, positionRelated));
                        writer.Write("{0}:{1} ", i + 1, this[i].IfContainedInSequence(line, positionRelated)?"1":"0");
                    }
                    //writer.WriteLine("{0}:{1}", Count, this[Count - 1].CountInSequnece(line, positionRelated));
                    writer.WriteLine("{0}:{1}",Count, this[Count-1].IfContainedInSequence(line, positionRelated)?"1":"0");
                }
            }
        }

        public void UpdateSupport(IEnumerable<string> sourceFileLines, bool isAbsolutePosition) {
            ProgressStatus status = new ProgressStatus(this.Count, "Updating support number of each LCV");
            foreach (LCS item in this) {
                StepChange(status);
                item.ResetSupportNumber();
                foreach (string sequence in sourceFileLines) {
                    item.Support += item.CountInSequnece(sequence, isAbsolutePosition);
                }
                item.DeleteRegex();
                status.ProgressOneStep();
            }
        }
        public void UpdateSupport2(IEnumerable<string> sourceFileLines, bool isAbsolutePosition) {
            int totalCount = this.Count;
            string SEQ = string.Join(Environment.NewLine, sourceFileLines.ToArray());
            ProgressStatus status = new ProgressStatus(totalCount, string.Format( "New Updating support2 number of each LCV from {0} sequences", totalCount));

            foreach (LCS item in this) {
                StepChange(status);
                item.ResetSuppor2tNumber();
                item.Support2 += Regex.Matches(SEQ, item.RegexString).Count;
                //foreach (string sequence in sourceFileLines) {
                //    //item.Support2 += item.CountInSequencePositionRelative(sequence);
                //}
                item.DeleteRegexString();
                status.ProgressOneStep();
            }
            
            /*
            this.ForEach(o => o.ResetSuppor2tNumber());
            foreach (string sequence in sourceFileLines) {
                StepChange(status);
                this.ForEach(o => o.Support2 += o.CountInSequencePositionRelative(sequence));
                status.ProgressOneStep();
            }
             * */

        }
        public void CalculateSupportDifference() {
            this.ForEach(o => {
                if (o.Support2.HasValue) {
                    o.SupportDifference = o.Support - o.Support2.Value;
                }
            });
        }
        public void CalculateSupportQuotient() {
            this.ForEach(o => {
                if (o.Support2.HasValue) {
                    o.SupportQuotient = o.Support * 1.0 / o.Support2.Value;
                }
            });
        }

        public void CalculateCovery(string referenceFile, string ouputFile, bool isAbsolutePosition) {
            var sequences = referenceFile.ReadAllLines().Where(o => o.Length > 0).ToList();
            var lcvs = this.ToList();
            int TotalCount = sequences.Count;
            int count = 0;
            var results = lcvs.Select(o => {
                count += sequences.RemoveAll(seq => o.IfContainedInSequence(seq, isAbsolutePosition));
                return new { LCV = o, Count = count, Ratio = count * 1.0 / TotalCount };
            });
            ouputFile.WriteFileAction( writer => {
                writer.WriteLine("{0} LCVs for {1} sequences.\t[{2}]", this.Count, TotalCount, DateTime.Now);
                foreach (var item in results) {
                    writer.WriteLine(string.Format("{0}\t{1}\t{2}", item.LCV, item.Count, item.Ratio));
                }
            });
        }

        #region ISerializable 成员

        public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) {
            SerializationWriter writer = new Xww.IO.Serialize.SerializationWriter();
            writer.Write(GenerateInfo);
            writer.Write(this.Count);
            foreach (LCS item in this) {
                writer.Write(item.Support);
                writer.WriteNullable(item.Support2);
                writer.WriteNullable(item.SupportDifference);
                writer.WriteNullable(item.SupportQuotient);
                writer.WriteNullable(item.PValue);
                writer.Write(item.AAs);
                writer.Write(item.Locations);
            }
            //writer.Write(this);
            info.AddValue("data", writer.ToArray());
        }
        private LCSCollection(SerializationInfo info, StreamingContext context) {
            Xww.IO.Serialize.SerializationReader reader = new Xww.IO.Serialize.SerializationReader(
                (byte[])info.GetValue("data", typeof(byte[])));
            GenerateInfo = reader.ReadString();
            //base.AddRange(reader.ReadList<LCS>());
            int count = reader.ReadInt32();
            for (int i = 0; i < count; i++) {
                LCS item = new LCS {
                    Support = reader.ReadInt32(),
                    Support2 = reader.ReadNullableInt32(),
                    SupportDifference = reader.ReadNullableDouble(),
                    SupportQuotient = reader.ReadNullableDouble(),
                    PValue = reader.ReadNullableDouble(),
                    AAs = reader.ReadCharArray(),
                    Locations = reader.ReadInt32Array()
                };
                base.Add(item);
            }
        }

        #endregion
    }
}
